# -*- coding: utf-8 -*-
"""
Created on Mon Jun 24 10:37:16 2024

@author: shaw

绘制螺旋线，有时会运行失败，
是否因为随机参数不合适？可否限制？
"""

# math可用np中的函数代替？
import random, argparse, turtle
# import sys, math
import numpy as np
from PIL import Image
# from Pillow import Image
from datetime import datetime

# 干什么用的？
# from fractions import gcd


class Spiro:
    '''
    生成螺旋线
    '''
    
    # 构造函数中可直接调用方法设置参数，不用逐一设置
    def __init__(self, xc, yc, col, R, r, l):
        
        # 新建一个Turtle对象，用来画图
        # Turtle对象是画图时的指针
        self.t = turtle.Turtle()
        # 设置 指针 形状为海龟
        self.t.shape('turtle')
        
        # 类内新增的参数，不需要return，类内的方法都可以使用
        self.step = 5
        self.drawing_complete = False
        
        # 调用方法设置参数
        self.set_params(xc, yc, col, R, r, l)
        self.restart()
    
        

        
    
        
    # 设置参数的方法，需要引用外部参数
    # Spiro对象在Spiro_animator中生成，参数在Spiro_animator中产生随机值
    def set_params(self, xc, yc, col, R, r, l):
        
        self.xc = xc
        self.yc = yc
        
        # 生成 时 限定为整数是否更好？
        self.R = int(R)
        self.r = int(r)
        self.l = l
        self.col = col
        
        # 求最大公约数
        # 如此更改的依据为python3.9将gcd函数从fractions改到了math文件中
        gcd_val = np.gcd(self.r, self.R)
        
        # 一条完整螺旋线，小圆需要转动的圈数，//整除符号
        self.nrot = self.r // gcd_val
        # 在Python中，当进行除法运算时，如果要确保结果是浮点数而不是整数，可以在分子或分母中的任意一个加上float()来将其转换为浮点数。这是因为在Python 2.x 版本中，整数除法会得到整数结果，而在 Python 3.x 版本中，整数除法会得到浮点数结果。
        self.k = r / float(R)
        self.t.color(*col)
        
        self.a = 0

        
        
    # 重新开始绘图，重开时，指针已移动1步，不是从xc,yc开始 
    def restart(self):
        self.drawing_complete = False
        self.t.showturtle()
        
        # 抬起画笔，停止绘画
        self.t.up()
        R, k, l = self.R, self.k, self.l
        a = 0.0
        # x = R*((1-k)*np.cos(a) + l*k*np.cos((1-k)*a/k))
        # y = R*((1-k)*np.sin(a) - l*k*np.sin((1-k)*a/k))
        self.calculate_position = lambda a, R, k, l: (R*((1-k)*np.cos(a) + l*k*np.cos((1-k)*a/k)),
                                                      R*((1-k)*np.sin(a) - l*k*np.sin((1-k)*a/k)))
        x, y = self.calculate_position(a, R, k, l)
        self.t.setpos(self.xc+x, self.yc+y)
        
        # 放下画笔，开始绘画
        self.t.down()
        
        
    # 绘图
    def draw(self):
        
        R, k, l = self.R, self.k, self.l
        for i in range(0, 360*self.nrot+1, self.step):
            a = np.radians(i)
            x, y = self.calculate_position(a, R, k, l)
            self.t.setpos(self.xc+x, self.yc+y)
            self.t.hideturtle()
            
            
    # 按step更新绘画，动画使用？
    def update(self):
        if self.drawing_complete:
            return
        
        self.a += self.step
        R, k, l = self.R, self.k, self.l
        a = np.radians(self.a)

        x, y = self.calculate_position(a, R, k, l)
        self.t.setpos(self.xc+x, self.yc+y)
        
        # 判断绘画完成
        if self.a >= 360*self.nrot:
            self.drawing_complete = True
            self.t.hideturtle()
            
            
    def clear(self):
        self.t.clear()
        
        
        
class Spiro_animator:
    '''
    生成螺旋线动画
    '''
    
    def __init__(self, n):
        # 时间，单位毫秒
        self.delta_t = 10
        
        # 默认值400pixel
        self.wildth = turtle.window_width()
        # 默认值300pixel
        self.height = turtle.window_height()
        self.spiros = []
        for i in range(n):
            r_params = self.gen_rand_params()
            spiro = Spiro(*r_params)
            self.spiros.append(spiro)
        
        # 每经过self.delta_t，调用方法self.update
        turtle.ontimer(self.update, self.delta_t)
          
          
          
    def restart(self):
        for spiro in self.spiros:
            spiro.clear()
            r_params = self.gen_rand_params()
            spiro.set_params(*r_params)
            spiro.restart()
            
            
            
    def gen_rand_params(self):
        width, height = self.wildth, self.height
        # 返回随机整数
        R = random.randint(50, min(width, height)//2)
        r = random.randint(10, 9*R//10)
        # 返回指定范围的随机小数
        l = random.uniform(.1, .9)
        xc = random.randint(-width//2, width//2)
        yc = random.randint(-height//2, height//2)
        col = (random.random(), random.random(), random.random())
        
        return (xc, yc, col, R, r, l)
    
 
    
    def update(self):
        
        # 计算几条螺旋线绘制完成
        n_complete = 0
        for spiro in self.spiros:
            spiro.update()
            if spiro.drawing_complete:
                n_complete += 1
                
        if n_complete == len(self.spiros):
            self.restart()
            
        turtle.ontimer(self.update, self.delta_t)
        
    
    def toggle_turtles(self):
        for spiro in self.spiros:
            if spiro.t.isvisible():
                spiro.t.hideturtle()
            else:
                spiro.t.showturtle()
                
        
          
          
def save_drawing():
    
    turtle.hideturtle()
    date_str = (datetime.now()).strftime('%d%b%Y-%H%M%S')
    f_name = 'spiro-' + date_str
    print(f'将绘图保存为{f_name}.png')
    
    # 新建 画布对象
    canvas = turtle.getcanvas()
    canvas.postscript(file = f_name+'.eps')
    
    img = Image.open(f_name + '.eps')
    img.save(f_name + '.png', 'png')
    
    turtle.showturtle()
    
    
def main():
    print('正在生成螺旋线……')
    
    desc_str = '''
    此程序使用Turtle模块绘制螺旋线，如果不输入参数，将绘制随机的螺旋线。
    
    参数：
    R: 外圆半径
    r: 内圆半径
    l: 比例系数
    '''
    
    # 这行代码创建了一个 ArgumentParser 对象，用于解析命令行参数。description参数用于指定程序的描述信息，它将在打印帮助信息时显示。
    parser = argparse.ArgumentParser(description=desc_str)
    
    # 这行代码定义了一个名为 --sparams 的命令行参数。nargs=3 表示该参数接受三个值，dest='sparams' 指定了参数解析后的存储位置，required=False 表示这个参数不是必须的，help 参数提供了关于参数的描述信息。
    # 使用参数执行程序示例: %run 万花尺.py --sparams 30 13 4.9
    
    parser.add_argument('--sparams', nargs=3, dest='sparams', required=False,
                        help='sparams中的3个参数是： R, r, l.  ')
    # 这行代码解析命令行参数，并将结果存储在 args 对象中。
    args = parser.parse_args()
    
    # turtle.setup(width = .8)
    # turtle.setup(width=.8, height=.8)
    turtle.setup(width=800, height=600)

    turtle.shape('turtle')
    turtle.title('螺旋线')
    
    # 这行代码用于绑定键盘事件。当用户按下键盘上的"s"键时，会调用名为save_drawing的函数。
    turtle.onkey(save_drawing, 's')
    # 这个函数用于开始监听键盘事件，以便触发相应的操作。
    turtle.listen()
    turtle.hideturtle()
    
    
    # 如果 输入 参数,按参数生成1条螺旋线
    if args.sparams:
        params = [float(x) for x in args.sparams]
        
        # 颜色黑色
        col = (.0, .0, .0)
        spiro = Spiro(0, 0, col, *params)
        spiro.draw()
        
    # 不输入 参数,随机 生成4条螺旋线   
    else:
        spiro_an = Spiro_animator(4)
        turtle.onkey(spiro_an.toggle_turtles, 't')
        turtle.onkey(spiro_an.restart, 'space')
        
    # 在调用了 turtle.mainloop() 之后，窗口将一直保持打开状态，并且可以响应用户的操作，直到用户关闭窗口或者程序显式地调用 turtle.bye() 来关闭窗口。
    turtle.mainloop()
    
    
if __name__ == '__main__':
    main()
    
    
